<template>
    <div ref="_container" class="_container" @drop="onDrop">
        <VueFlow :connection-mode="ConnectionMode.Strict" :default-edge-options="{markerEnd: {type: MarkerType.ArrowClosed, color:'#5822d9'}}"
                 :default-viewport="{ zoom: 0.8 }" :edges="edges"
                 :node-types="NodeTypes" :delete-key-code="onDelete" :nodes="nodes"
                 auto-connect @dragleave="onDragLeave" :connection-radius="30"
                 @dragover="onDragOver" :apply-default="false">
            <DropzoneBackground :style="{
          backgroundColor: isDragOver ? '#e7f3ff' : 'transparent',
          transition: 'background-color 0.2s ease',
        }">
                <p v-if="isDragOver" style="font-size: 30px;color: #999">拖拽至此处</p>

            </DropzoneBackground>
            <MiniMap pannable zoomable/>
            <Controls :show-fit-view="false" :show-interactive="false" :show-zoom="false" class="tool" position="bottom-center">
                <n-button-group>
                    <n-button strong secondary type="tertiary" @click="zoomIn">
                        <jb-icon :local="true" icon="jam:plus"></jb-icon>
                        放大
                    </n-button>
                    <n-button strong secondary type="tertiary" @click="zoomOut">
                        <jb-icon :local="true" icon="jam:minus"></jb-icon>
                        缩小
                    </n-button>
                    <n-button strong secondary type="tertiary" @click="fitView">
                        <jb-icon :local="true" icon="jam:arrows-fullscreen"></jb-icon>
                        适配视图
                    </n-button>
                    <n-button strong secondary :type="lock?'warning':'tertiary'" @click="toggleLock">
                        <jb-icon :local="true" icon="jam:shopping-bag-f"></jb-icon>
                        {{ lock ? '已锁定' : '锁定' }}
                    </n-button>
                    <n-button strong secondary type="tertiary" @click="toggleFullscreen ">
                        <jb-icon :local="true" icon="jam:screen-f"></jb-icon>
                        全屏
                    </n-button>
                    <n-popover style="border-radius: 10px;width: 500px;height: 600px;" trigger="hover">
                        <template #trigger>
                            <n-button strong secondary type="tertiary">
                                <template #icon>
                                    <jb-icon :local="true" icon="jam:plus-circle"></jb-icon>
                                </template>
                                添加节点
                            </n-button>
                        </template>
                        <span>
                        <node-list></node-list>
						</span>
                    </n-popover>
                    <n-button strong secondary type="tertiary" @click="testRun">
                        <jb-icon :local="true" icon="fluent:triangle-right-48-filled"></jb-icon>
                        试运行
                    </n-button>
                    <n-popover style="border-radius: 10px;width: 900px;height: 700px;overflow-y: auto" trigger="hover">
                        <template #trigger>
                            <n-button strong secondary>
                                <jb-icon :local="true" icon="bxs:paper-plane"></jb-icon>
                                发布
                            </n-button>
                        </template>
                        <span>
                        <Publish :appId="appId" theme="small"></Publish>
						</span>
                    </n-popover>


                </n-button-group>

            </Controls>
            <!-- Tooltip 容器 -->
            <div ref="_tooltip" class="node_tooltip"></div>

            <template #edge-custom="customEdgeProps">
                <CustomEdge v-bind="customEdgeProps" />
            </template>
        </VueFlow>

        <div>

        </div>
        <div v-if="debugMode" :class="{min:minDebug}" class="debug_container flex-col">
            <div class="header shrink-0 flex justify-between px-4 bg-gray-100 items-center">
                <div class="header-left flex items-center">
                    <jb-icon :local="true" icon="codicon:debug-all" width="20"></jb-icon>
                    <div>
                        <span class="text ml-2 text-gray-700 font-medium">试运行</span>
                        <span v-show="minDebug == false" class="text-gray-400 text-xs ml-2"
                              style="vertical-align: -1px">每次节点变更，都需要重新点击试运行</span>
                    </div>
                </div>
                <div class="flex">
                    <jb-icon :local="true" v-if="minDebug == false" class="cursor-pointer" icon="typcn:minus" width="20"
                             @click="minDebug=true"></jb-icon>
                    <jb-icon :local="true" v-if="minDebug" class="cursor-pointer" icon="octicon:screen-full-16"
                             width="20"
                             @click="minDebug=false"></jb-icon>
                    <jb-icon :local="true" v-if="minDebug == false" class="cursor-pointer ml-2 close_btn" icon="ep:close-bold"
                             width="20" @click="closeDebug"></jb-icon>
                </div>

            </div>
            <div ref="chatContainer" class="chat_container flex-1 overflow-hidden"></div>
        </div>

    </div>

</template>
<script>

</script>
<script setup>
import markdownit from 'markdown-it'
import NodeList from './nodes/index.vue';
import Publish from '../../ai_app/cpns/app-tools/cpns/publish/index.vue'
import {
    onMounted,
    ref,
    watch,
    provide
} from 'vue'
import {
    VueFlow,
    useVueFlow,
    ConnectionMode,
    MarkerType
} from '@vue-flow/core'
import DropzoneBackground from './DropzoneBackground.vue'
import useDragAndDrop from './useDragDrop.js'
import {
    getNodeByType,
    NodeTypes
} from "./nodes/node-def.js";
import {
    JBoltApi
} from "@/service/request/index.js";
import {
    ControlButton,
    Controls
} from '@vue-flow/controls'
import {
    MiniMap
} from '@vue-flow/minimap'
import {nextId} from "@/utils/common/snowflakeIdGenerator.js";
import {storeToRefs} from "pinia";
import {useWebSocketStore} from "@/stores/websocket.js";
import AiChat from "@/components/_builtin/jb-ai-chat/index.js";
import {useRoute} from 'vue-router';
import {useMessage} from 'naive-ui'
import {tabStore} from '@/stores/tabStore.js';
import texmath from "markdown-it-texmath";
import linkAttributes from "markdown-it-link-attributes"
import katex from "katex";
import CustomEdge from './edge/CustomEdge.vue'
const message = useMessage()


// 获取当前路由信息
const route = useRoute();
const currentRoute = route.path;
// const appId = ref(1); //模拟应用id
// 从路由参数中提取 appId
const appId = ref(route.params.appId);
const welcomeTip = ref('');
const {
    onConnect,
    onInit,
    addEdges,
    onNodesChange,
    getNode,
    applyNodeChanges,
    applyEdgeChanges,
    onEdgesChange,
    getNodes,
    getEdges,
    setEdges,
    viewport,
    zoomIn, zoomOut, fitView
} = useVueFlow(appId.value)
const {
    onDragOver,
    onDrop,
    onDragLeave,
    isDragOver
} = useDragAndDrop()

const _container = ref(null);
const _tooltip = ref(null);
watch(() => _container.value, (dom) => {
    if (dom) {
        dom.addEventListener("mouseover", function (e) {
            if (e.target && e.target.closest(".vue-flow__handle.source")) {
                const sourceElement = e.target.closest(".vue-flow__handle.source");
                showTooltip(sourceElement);
            } else {
                hideTooltip()
            }
        })
    }
})
const zoom = ref(1);
provide('zoom', zoom);
//监听画布缩放
watch(() => viewport.value.zoom, (newZoom, oldZoom) => {
    zoom.value = newZoom;
    console.log('viewport', zoom.value)
});


function showTooltip(sourceElement) {
    const branch = sourceElement.dataset.handleid;
    switch (branch) {
        case "success":
            _tooltip.value.innerText = "当节点执行成功";
            break;
        case "fail":
            _tooltip.value.innerText = "当节点执行失败";
            break;
        case 'INNER_NO_MATCH':
            _tooltip.value.innerText = "未匹配到任何意图";
            break;
        case 'klb_match':
            _tooltip.value.innerText = "知识库匹配到数据";
            break;
        case 'klb_no_match':
            _tooltip.value.innerText = "知识库未匹配到数据";
            break;
        case 'func_call_success':
            _tooltip.value.innerText = "function执行成功";
            break;
        case 'func_no_match':
            _tooltip.value.innerText = "未匹配到function";
            break;
        default:
            return;
    }


    // 计算位置
    const targetRect = sourceElement.getBoundingClientRect();
    const tooltipRect = _tooltip.value.getBoundingClientRect();
    _tooltip.value.style.top = `${targetRect.top - tooltipRect.height - 5}px`;
    _tooltip.value.style.left = `${targetRect.left + (targetRect.width) / 2}px`;
    _tooltip.value.style.visibility = 'visible';
    _tooltip.value.style.opacity = 1;
    _tooltip.value.style.userSelect = 'none';
}

function hideTooltip() {
    if (_tooltip.value) {
        _tooltip.value.style.visibility = 'hidden';
        _tooltip.value.style.opacity = 0;
    }
}

// 切换全屏的函数
const toggleFullscreen = () => {
    const element = _container.value;
    if (document.fullscreenElement) {
        console.log('当前是全屏状态')
        // 如果当前是全屏状态，则退出全屏
        if (document.exitFullscreen) {
            document.exitFullscreen();
        } else if (document.webkitExitFullscreen) {
            document.webkitExitFullscreen();
        } else if (document.msExitFullscreen) {
            document.msExitFullscreen();
        }
    } else {
        console.log('当前不是全屏状态', element)
        // 如果当前不是全屏状态，则进入全屏
        if (element.requestFullscreen) {
            element.requestFullscreen();
        } else if (element.webkitRequestFullscreen) {
            element.webkitRequestFullscreen();
        } else if (element.msRequestFullscreen) {
            element.msRequestFullscreen();
        }
    }
};


//-------------节点相关------------------

const nodes = ref([])

const edges = ref([])

const lock = ref(false); //是否锁定

function toggleLock() {
    lock.value = !lock.value;
}

onConnect(params => {
    console.debug("新增链接", params);
    const newEdge = {
        ...params, // 包含 source, target, sourceHandle, targetHandle
        id: `e${params.source}_${params.sourceHandle}-${params.target}`, // 生成一个简单的 ID
        type:'custom',
        animated: true,
    }
    addEdges([newEdge]) // 使用 addEdges 添加新创建的边
})
let nodeUpdateTimer = null;

function updateNodesData() {
    if (nodeUpdateTimer) {
        clearTimeout(nodeUpdateTimer);
        nodeUpdateTimer = null;
    }
    nodeUpdateTimer = setTimeout(() => {
        updateAppDesign('nodes', getNodes.value.map(({
                                                         id,
                                                         type,
                                                         data,
                                                         position
                                                     }) => ({
            id,
            type,
            data,
            position
        })))
    }, 1000)
}

provide("updateNodesData", updateNodesData)

onNodesChange((events) => {

    if (lock.value) {
        //如果锁定了
        return;
    }
    const nextChanges = []
    for (let e of events) {
        if (e.type == 'remove') {
            // let node = getNode.value(e.id);
            // if (node?.type == 'start_user_input' || node?.type == 'app_config') {
            //这两个节点禁止删除
            continue
            // }
        }
        nextChanges.push(e);
    }
    applyNodeChanges(nextChanges)

    updateNodesData();
})

let edgeUpdateTimer = null;

function updateEdgesData() {

    if (edgeUpdateTimer) {
        clearTimeout(edgeUpdateTimer);
        edgeUpdateTimer = null;
    }
    edgeUpdateTimer = setTimeout(() => {
        updateAppDesign('edges', getEdges.value.map(({
                                                         id,
                                                         source, sourceHandle,
                                                         target,
                                                         label
                                                     }) => ({
            id,
            source, sourceHandle,
            target,
            label
        })))
    }, 2000)
}

provide("updateEdgesData", updateEdgesData)
onEdgesChange((events) => {
    if (lock.value) return;
    console.debug("edgesEvent", events);
    applyEdgeChanges(events)
    updateEdgesData();

})

function updateAppDesign(field, value) {
    console.debug("开始同步数据", field, value);
    //调后端接口
    JBoltApi.tryPost(`/aiAppDesign/update`, {
        appId: appId.value,
        fieldsName: field,
        fieldsValue: JSON.stringify(value)
    }).then(res => {
        if (field == 'nodes') {
            // 遍历对象数组寻找app_config类型并获取welcomeTip值
            for (const item of value) {
                if (item.type === "app_config" && item.data && item.data.uiConfig && item.data.uiConfig.welcomeTip) {
                    welcomeTip.value = item.data.uiConfig.welcomeTip;
                    break;
                }
            }

        }
    });


}


function loadAppDesign() {
    JBoltApi.tryGet(`/aiAppDesign/getById/${appId.value}`).then(res => {
        var appDesign = res.data;
        var db_nodes = appDesign.nodes ? JSON.parse(appDesign.nodes) : [];
        var db_edges = appDesign.edges ? JSON.parse(appDesign.edges) : [];
        if (db_nodes != null && db_nodes.length > 0) {
            //处理outputToClient,兼容旧数据
            for (let i = 0; i < db_nodes.length; i++) {
                let node = db_nodes[i];
                if (node.type == 'output_to_client') {
                    if (!node.data.outputToClientConfig) {
                        node.data = {
                            "outputToClientConfig": node.data,
                        }
                    }
                }
                if (node.type == 'app_config') { //取出welcomeTip欢迎语
                    if (node.data.uiConfig) {
                        if (node.data.uiConfig.welcomeTip) {
                            welcomeTip.value = node.data.uiConfig.welcomeTip;
                        }
                    }
                }
            }
            nodes.value = db_nodes;
        } else {
            nodes.value = [{
                id: nextId(),
                type: 'start_user_input',
                position: {
                    x: 200,
                    y: 500
                },
                data: {
                    outputField: 'prompt',
                },
                label: '开始节点',
                deletable: false
            }, {
                id: nextId(),
                type: 'app_config',
                position: {
                    x: 200,
                    y: 50
                },
                data: {
                    uiConfig: {
                        userAvatar: '',
                        assistantAvatar: '',
                        appLogo: '',
                        showAvatar: false,
                        welcomeTip: ''
                    }
                },
                label: 'App应用设置',
                deletable: false
            }]
        }
        if (db_edges != null && db_edges.length > 0) {
            console.log("关联数据", db_edges)
            setTimeout(() => { //延时渲染连线，node未渲染完，就渲染连线会报错
                edges.value = db_edges.map(item => ({...item,type:'custom', animated: true}));
                fitView({padding: 0.5})
            }, 100)
        }


    }).catch((err) => {
        console.error(err);
    })
}

onInit(() => {
    loadAppDesign()
    // provide('zoom', zoom);
})

/**
 * 当前激活的节点
 */
const activeNodeId = ref(null)

function updateActiveNode(nodeId) {
    activeNodeId.value = nodeId
}

function removeNode(nodeId) {
    nodes.value = getNodes.value.filter(node => node.id !== nodeId)
    updateNodesData();
    edges.value = getEdges.value.filter(edge => edge.source !== nodeId && edge.target !== nodeId)
    updateEdgesData();
}

provide("activeNodeId", activeNodeId)
provide("updateActiveNode", updateActiveNode)
provide("removeNode", removeNode)


//------------调试相关 ---------------------


const chatContainer = ref(null);
const chatInstance = ref(null);
const minDebug = ref(false)
const debugMode = ref(false) //是否开启debug模式
const webSocketStore = storeToRefs(useWebSocketStore())
const sessionId = ref(null)
const md = markdownit({
    html: true,
    breaks: true,
    linkify: true,
    typographer: true
});
md.use(texmath, {
    engine: katex,
    delimiters: [
        "dollars", "brackets"
    ]
})
// 使用 linkAttributes 插件，并配置目标属性和 rel 属性
md.use(linkAttributes, {
    pattern: /https?:\/\/(www\.)?/i, // 匹配所有 HTTP/HTTPS 链接
    attrs: {
        target: '_blank', // 设置所有链接在新标签页打开
        rel: 'noopener' // 增加这个属性可以提高安全性
    }
})

watch(() => webSocketStore.ws.value, () => {
    console.debug("websock重连")
    if (chatInstance.value) {
        chatInstance.value.updateWebSocket(webSocketStore.ws.value)
    }
})
watch(() => chatContainer.value, (newV) => {
    //dom初始化好了
    if (newV && appId.value && !chatInstance.value) {
        loadChatInstance()
    }
})

/**
 * 关闭debug模式
 */
function closeDebug() {
    if (chatInstance.value) {
        chatInstance.value.destroy();
        chatInstance.value = null;
    }
    minDebug.value = false;
    debugMode.value = false;
}

function testRun() {
    JBoltApi.tryGet(`/aiAppDesign/testRun/${appId.value}`).then(res => {
        closeDebug();
        JBoltApi.tryPost('/aiAppSession/add', {
            name: '试运行会话',
            appId: appId.value,
            isDebug: true
        }).then(res => {
            debugMode.value = true;
            sessionId.value = res.data.id;
        })
    })
}


function loadChatInstance() {
    //初始化aichat
    chatInstance.value = new AiChat(chatContainer.value, {
        // websocketUrl: 'ws://192.168.31.118:8013/ws',
        websocketInstance: webSocketStore.ws.value,
        app: appId.value,
        welcomeTip: welcomeTip.value,
        sessionId: sessionId.value,
        global: {
            debugMode: true
        },
        showAvatar: false,
        onClearMsg: async (sessionId) => {
            let res = await JBoltApi.tryGet(`/aiAppMessage/deleteBySessionId/${sessionId}`);
            return true;
        },
        onHistoryLoad: async (startId, global) => {
            return {status: 'over', msgs: null}
        },
        onMsgToDelete: async (id) => {
            console.debug("删除消息", id)
            let {
                result
            } = await JBoltApi.get(`/aiAppMessage/delMsgById/${id}`)
            if (result.code == 0) {
                message.success("删除成功");
                return true;
            } else {
                // message.error("删除失败");
                return false;
            }
        },
        onMarkdownRender: (markdown) => {
            // console.log("开始渲染", markdown)
            return md.render(markdown);
        },
    })

}


//---------其他 ------------------

const app = ref(null)

onMounted(() => {
    loadAppInfo();
})

/**
 * 加载应用信息
 */
function loadAppInfo() {
    JBoltApi.tryGet(`/aiApp/getById/${appId.value}`).then((res) => {
        app.value = res.data;
        updateTabName();

    });
}

/**
 * 更新导航标签label
 */
function updateTabName() {
    console.debug("currentRoute", currentRoute);
    tabStore.updateTabTitle(currentRoute, `${app.value.name}-编排`);
}

function onDelete(e) {
    return e.keyCode == 46 || e.keyCode == 8;
}

</script>
<style scoped>
._container {
    margin: 0;
    background: #f8fafc;
    position: relative;
}

:deep(.vue-flow__controls) {
    display: flex !important;
    flex-direction: row !important;
}


.debug_container {
    width: 400px;
    height: 80%;
    position: absolute;
    right: 30px;
    bottom: 30px;
    z-index: 9999;
    box-shadow: 0 0 16px 4px rgba(0, 0, 0, .1);
    border-radius: 8px;
    overflow: hidden;
    transition: all .3s;
}

.debug_container .header {
    height: 40px;
}

.debug_container.min {
    height: 40px;
    width: 160px;
}

.tool button {
    background-color: #fff;
}

.tool button:hover {
    background-color: #daf0e4;
    color: #1aa159;
}


</style>

<style>
@import '@vue-flow/core/dist/style.css';
@import '@vue-flow/core/dist/theme-default.css';
@import "@/styles/node.css";
@import '@vue-flow/controls/dist/style.css';
@import '@vue-flow/minimap/dist/style.css';
</style>
